package xdp

import (
	"bytes"
	"fmt"
	"log"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/rlimit"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG  -cflags $BPF_CFLAGS bpf ../../ebpf/xdp.c  -- -I../../headers

// App stores ebpf programs and maps together with the desired state
const (
	progName      = "xdp/xdp_ip_filter"
	BlacklistPath = "/sys/fs/bpf"
)

// Hook provides a set of operations that allow for managing the execution of the XDP program
// including attaching it on the network interface, harvesting various statistics or removing
// the program from the interface.
type Hook struct {
	mod *bpfObjects
	//link link.Link
}

// NewHook constructs a new instance of the XDP hook from provided XDP code.
func NewHook() (*Hook, error) {
	// Allow the current process to lock memory for eBPF resources.
	if err := rlimit.RemoveMemlock(); err != nil {
		log.Fatal(err)
		return nil, err
	}
	// Load pre-compiled programs and maps into the kernel.
	objs := bpfObjects{}
	var opts ebpf.CollectionOptions
	if err := loadIProute2BpfObjects(&objs, &opts); err != nil {
		log.Fatalf("loading objects: %v", err)
		return nil, err

	}

	return &Hook{mod: &objs}, nil
}

func loadIProute2Bpf() (*ebpf.CollectionSpec, error) {
	reader := bytes.NewReader(_BpfBytes)
	spec, err := ebpf.LoadCollectionSpecFromReader(reader)
	if err != nil {
		return nil, fmt.Errorf("can't load bpf: %w", err)
	}
	for _, m := range spec.Maps {
		if m.Extra == nil || m.Extra.Len() == 0 {
			logger.Fatalf("Expected extra bytes in map %s", m.Name)
		}
		//fmt.Printf("have Extra %v", m.Extra)
		m.Extra = nil
	}
	return spec, err
}
func loadIProute2BpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
	spec, err := loadIProute2Bpf()
	if err != nil {
		logger.Fatal(err)
		return err
	}
	for _, mapSpec := range spec.Maps {
		mapSpec.Pinning = ebpf.PinByName
		opts.Maps.PinPath = BlacklistPath
		// if mapSpec.Pinning != ebpf.PinNone {
		// 	//fmt.Printf("have ping %s", opts.Maps.PinPath)
		// 	opts.Maps.PinPath = BlacklistPath
		// 	break
		// }
	}

	return spec.LoadAndAssign(obj, opts)
}

// Attach loads the XDP program to specified interface.
func (h *Hook) Attach(dev string) error {
	// before we proceed with attaching make sure that the
	// provided device (interface) is present on the machine
	// attempt attach the XDP program
	err := h.addXdpToLink(dev)
	if err != nil {
		return fmt.Errorf("couldn't attach XDP program to %s interface", dev)
	}
	return err
}

// Remove unloads the XDP program from the interface.
func (h *Hook) Remove(dev string) error {
	err := h.delXdpFromLink(dev)
	return err
}

// Attach loads the XDP program to specified interface.
// func (h *Hook) Attach_old(dev string) (link.Link, error) {
// 	// before we proceed with attaching make sure that the
// 	// provided device (interface) is present on the machine
// 	ifaces, err := net.Interfaces()
// 	dev_index := 0
// 	if err != nil {
// 		return nil, fmt.Errorf("couldn't obtain the list of interfaces: %v", err)
// 	}
// 	ok := false
// 	for _, i := range ifaces {
// 		if i.Name == dev {
// 			dev_index = i.Index
// 			ok = true
// 			break
// 		}
// 	}
// 	if !ok {
// 		return nil, fmt.Errorf("%s interface is not present. Please run `ip a` to list available interfaces", dev)
// 	}
// 	// attempt attach the XDP program
// 	l, err := link.AttachXDP(link.XDPOptions{
// 		Program:   h.mod.bpfPrograms.XdpIpFilter,
// 		Interface: dev_index,
// 		Flags:     link.XDPGenericMode,
// 	})
// 	if err != nil {
// 		return nil, fmt.Errorf("couldn't attach XDP program to %s interface", dev)
// 	}
// 	//h.link = l
// 	return l, err
// }

// Remove unloads the XDP program from the interface.
// func (h *Hook) Remove_old(dev string) error {
// 	//err := h.link.Close()
// 	// ifaces, err := net.Interfaces()
// 	// dev_index := 0
// 	// if err != nil {
// 	// 	return fmt.Errorf("couldn't obtain the list of interfaces: %v", err)
// 	// }
// 	// ok := false
// 	// for _, i := range ifaces {
// 	// 	if i.Name == dev {
// 	// 		dev_index = i.Index
// 	// 		ok = true
// 	// 		break
// 	// 	}
// 	// }
// 	// if !ok {
// 	// 	return fmt.Errorf("%s interface is not present. Please run `ip a` to list available interfaces", dev)
// 	// }
// 	err := link.RawDetachProgram(link.RawDetachProgramOptions{
// 		Program: h.mod.bpfPrograms.XdpIpFilter,
// 		Target:  h.mod.bpfPrograms.XdpIpFilter.FD(),
// 		Attach:  ebpf.AttachXDP,
// 	})
// 	return err
// }

// Close closes the underlying eBPF module by disposing any allocated resources.
func (h *Hook) Close() error {
	err := h.mod.Close()
	if err != nil {
		return fmt.Errorf("close fail: %v", err)
	}
	return err
}
